package plug;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.geom.Point2D;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.logging.Logger;

import creatures.IColorStrategy;
import creatures.ICreature;
import creatures.IEnvironment;

public class CreaturePluginFactory {
	public static final int MAX_CREATURE = 50;
	
	private PluginLoader pluginLoader;
	private final String pluginDir = "fr.unice.project.genie.logiciel/myplugins/";
	
	protected Map<String,Constructor<? extends ICreature>>  constructorMap;
	
	/**
	   * logger facilities to trace plugin loading...
	   */
	private static Logger logger = Logger.getLogger("plug.CreaturePluginFactory");
	
	/**
	 * singleton for the abstract factory
	 */
	private static CreaturePluginFactory _singleton;
	
	private double maxSpeed;
	
	private CreaturePluginFactory(double inMaxSpeed) {
		try {
    		pluginLoader = new PluginLoader(pluginDir,ICreature.class);
    	}
    	catch (MalformedURLException ex) {
    	}
		maxSpeed=inMaxSpeed;
		constructorMap = new HashMap<String,Constructor<? extends ICreature>>();
    	load();
	}

	public static void init(double inMaxSpeed) {
		if (_singleton != null) {
            throw new RuntimeException("CreatureFactory already created by " 
				  + _singleton.getClass().getName());
        } else {
             _singleton = new CreaturePluginFactory(inMaxSpeed);
        }
			
	}

	public static CreaturePluginFactory getInsance() { return _singleton;}
	
	public Map<String, Constructor<? extends ICreature>> getConstructorMap() {
		return constructorMap;
	}
	
	public void load() {
		pluginLoader.loadPlugins();
		constructorMap.clear();
		buildFactoriesMap();
		
	}
	
	@SuppressWarnings("unchecked")
	public void buildFactoriesMap() {
		for (Class<? extends IPlugin> p : pluginLoader.getPluginClasses()) {
			Constructor<? extends ICreature> c = null;
			try {				
				c = (Constructor<? extends ICreature>)
						p.getDeclaredConstructor(IEnvironment.class, Point2D.class, double.class, double.class, Color.class);
				c.setAccessible(true);
			} catch (SecurityException e) {
				logger.info("Cannot access (security) constructor for plugin" + p.getName());
				e.printStackTrace();
			} catch (NoSuchMethodException e) {
				logger.info("No constructor in plugin " + p.getName() + " with the correct signature");
				e.printStackTrace();
			}
			if (c != null)
				constructorMap.put(p.getName(),c);
		}

	}
	
	private final Random rand = new Random();
	
	/**
	 * 
	 * @param env
	 * @param count
	 * 		number of creatures 
	 * @param cs
	 * @param cstr
	 * @return
	 * 	a collection of creatures
	 */
	@SuppressWarnings("unchecked")
	public <T extends ICreature> Collection<T> createCreatures(IEnvironment env, int count, 
			IColorStrategy cs, Constructor<? extends ICreature> cstr) {
		Collection<T> creatures = new ArrayList<T>();		
		Dimension s = env.getSize();		
		for (int i=0; i<count; i++) {	
			// X coordinate
			double x = (rand.nextDouble() * s.getWidth());
			// Y coordinate
			double y = (rand.nextDouble() * s.getHeight());
			creatures.add((T) createCreature(env, cs, cstr, x, y));
		}		
		return creatures;
	}
	
	/**
	 * 
	 * @param env
	 * @param cs
	 * @param pluginName
	 * @param x
	 * @param y
	 * @return
	 * 	one creature knowing its position ({@code x};{@code y})
	 */
	@SuppressWarnings("unchecked")
	public <T extends ICreature> Collection<T> createOneCreature(IEnvironment env, 
			IColorStrategy cs, String pluginName,double x, double y) {
		Collection<T> creatures = new ArrayList<T>();					
		creatures.add((T) createCreature(env, cs, constructorMap.get(pluginName), x, y));		
		return creatures;
	}
	

	/**
	 * @param env
	 * @param cs
	 * @param cstr
	 * @param x
	 * @param y
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private <T extends ICreature> T createCreature(IEnvironment env,
			IColorStrategy cs, Constructor<? extends ICreature> cstr, double x,double y) {
		Dimension s = env.getSize();
		// X coordinate
		x = x - s.getWidth() / 2;
		// Y coordinate
		y = y - s.getHeight() / 2;
		// direction
		double direction = (rand.nextDouble() * 2 * Math.PI);
		// speed
		int speed = (int) (rand.nextDouble() * maxSpeed);			
		T creature = null;
		try {
			creature = (T) cstr.newInstance(env, new Point2D.Double(x,y), speed, direction, cs.getColor());
		} catch (Exception e) {
			logger.info("calling constructor " + cstr + " failed with exception " + e.getLocalizedMessage());
			e.printStackTrace();
		}
		return creature;
	}
	
	
	public <T extends ICreature> Collection<T> createCreatures(IEnvironment env, int count, 
			IColorStrategy cs, String pluginName)
	{
		return createCreatures(env, count, cs, constructorMap.get(pluginName));
	}

}
